#include <iostream>
#include <cassert>
#include <ctime>
#include <chrono>
#include <iomanip>
#include "logger.h"

#define CYB_DEFAULT_LOG_FORMAT	"[%L]: %m"

namespace cyb
{

static Logger s_defaultLogger;
Logger* g_log = &s_defaultLogger;

static std::string LevelToString( const LogLevel level )
{
	switch ( level )
	{
	case LogLevel::Debug:	return "Debug";
	case LogLevel::Info:	return "Info";
	case LogLevel::Warning:	return "Warning";
	case LogLevel::Error:	return "Error";
	};

	return "Unknown";
}

LogMessage::LogMessage( ILogger* loggerCallback, LogLevel level, const std::string& sourceFile, uint32_t sourceLine )
{
	m_logCallback = loggerCallback;
	m_level = level;
	m_sourceFile = sourceFile;
	m_sourceLine = sourceLine;
}

LogMessage::~LogMessage()
{
	m_logCallback->Write( this );
}

std::string LogMessage::String() const
{
	return m_stringStream.str();
}

LogLevel LogMessage::Level() const
{
	return m_level;
}

const std::string& LogMessage::SourceFile() const
{
	return m_sourceFile;
}

uint32_t LogMessage::SourceLine() const
{
	return m_sourceLine;
}

LogPolicySettings::LogPolicySettings( const std::shared_ptr<XmlElement> element ) :
	m_logDebug( false ),
	m_logInfo( false ),
	m_logWarning( false ),
	m_logError( false ),
	m_format( CYB_DEFAULT_LOG_FORMAT )
{
	assert( element != nullptr );
	bool logAll = false;
	
	element->QueryAttribute( "debug",	&m_logDebug,	XmlFlag::Optional );
	element->QueryAttribute( "info",	&m_logInfo,		XmlFlag::Optional );
	element->QueryAttribute( "warning",	&m_logWarning,	XmlFlag::Optional );
	element->QueryAttribute( "error",	&m_logError,	XmlFlag::Optional );
	element->QueryAttribute( "log_all",	&logAll,		XmlFlag::Optional );
	element->QueryAttribute( "format",	&m_format,		XmlFlag::Optional );

	if ( logAll )
	{
		m_logDebug		= true;
		m_logInfo		= true;
		m_logWarning	= true;
		m_logError		= true;
	}
}

bool LogPolicySettings::ShouldLog( const LogMessage* message )
{
	switch ( message->Level() )
	{
	case LogLevel::Debug:	return m_logDebug;
	case LogLevel::Info:	return m_logInfo;
	case LogLevel::Warning:	return m_logWarning;
	case LogLevel::Error:	return m_logError;
	}

	return false;
}

// TOTO: Replace the std::ostringstream for something faster
// Format explanation: %t=time %L=level %f=file %l=line %m=message %%=print '%'
std::string LogPolicySettings::GetFormattedString( const LogMessage* message )
{
	std::ostringstream stream;
	auto timePoint = std::chrono::system_clock::now();
	time_t time = std::chrono::system_clock::to_time_t( timePoint );

	for ( std::string::size_type i = 0; i < m_format.length(); i++ )
	{
		if ( m_format[i] == '%' )
		{
			switch ( m_format[i + 1] )
			{
			case 't': 
				stream <<  std::put_time( std::localtime( &time ), "%c" );
				break;
			case 'L':
				stream << LevelToString( message->Level() );
				break;
			case 'l':
				stream << message->SourceLine();
				break;
			case 'f':
				stream << message->SourceFile();
				break;
			case 'm':
				stream << message->String();
				break;
			case '%':
				stream << '%';
				break;
			default: break;
			}

			i++;
			continue;
		}

		stream << m_format[i];
	}

	stream << std::endl;
	return stream.str();
}

CommonLogPolicy::CommonLogPolicy( const std::shared_ptr<XmlElement> element ) :
	m_settings( element )
{
}

bool CommonLogPolicy::ShouldLog( const LogMessage* message )
{
	return m_settings.ShouldLog( message );
}

std::string CommonLogPolicy::GetFormattedString( const LogMessage* message )
{
	return m_settings.GetFormattedString( message );
}

ConsoleLogPolicy::ConsoleLogPolicy( const std::shared_ptr<XmlElement> element ) :
	CommonLogPolicy( element )
{
}

void ConsoleLogPolicy::Write( const std::string& buffer ) 
{
	std::cout << buffer;
}

FileLogPolicy::FileLogPolicy( const std::shared_ptr<XmlElement> element ) :
	CommonLogPolicy( element )
{
	std::string filename;
	element->QueryAttribute( "name", &filename, XmlFlag::Required );
	m_file.open( filename );
}

void FileLogPolicy::Write( const std::string& buffer )
{
	if ( m_file.is_open() )
	{
		m_file << buffer;
		m_file.flush();
	}
}

void Logger::Reset( const std::shared_ptr<XmlElement> element )
{
	assert( element != nullptr );	// assert, becouse error messages wont show up without policies 
	element->CheckElementName( "logging", cyb::XmlFlag::Required );

	const auto consoleElement = element->FirstChildElement( "console" );
	if ( consoleElement )
	{
		AddPolicy( std::make_unique<ConsoleLogPolicy>( consoleElement ) );
	}

	for ( const auto fileElement: element->ChildList( "file" ) )
	{
		AddPolicy( std::make_unique<FileLogPolicy>( fileElement ) );
	}
}

void Logger::AddPolicy( std::unique_ptr<ILogPolicy> logPolicy )
{
	m_policies.push_back( std::move( logPolicy ) );
}

void Logger::RemoveAllPolicies()
{
	m_policies.erase( m_policies.begin(), m_policies.end() );
}

void Logger::Write( const LogMessage* message ) 
{
	for ( auto& policy : m_policies )
	{
		if ( policy->ShouldLog( message ) )
		{
			policy->Write( policy->GetFormattedString( message ) );
		}
	}
}

}	// namespace cyb